//------------------------------------------------------------------------------
//  <copyright file="Point2D.cs" company="Microsoft Corporation">
// The MIT License (MIT)
// 
// Copyright (c) 2014, Microsoft Corporation
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy
//  of this software and associated documentation files (the "Software"), to deal
//  in the Software without restriction, including without limitation the rights
//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//  copies of the Software, and to permit persons to whom the Software is
//  furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in
//  all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//  THE SOFTWARE.
//  </copyright>
//------------------------------------------------------------------------------

namespace Microsoft.Robotics.Numerics
{
    using System;
    using System.Diagnostics;
    using System.Runtime.CompilerServices;
    using System.Runtime.InteropServices;
    using System.Runtime.Serialization;

    using Microsoft.Robotics.Numerics;

    /// <summary>
    /// A Point in 2D space.
    /// </summary>
    [DebuggerDisplay("<{x}, {y}>")]
    [DataContract]
    public struct Point2D
    {
        /// <summary>
        /// The X coordinate
        /// </summary>
        [DataMember]
        private readonly double x;

        /// <summary>
        /// The X coordinate
        /// </summary>
        [DataMember]
        private readonly double y;

        /// <summary>
        /// Initializes a new instance of the Point2D struct.
        /// </summary>
        /// <param name="that">Another Point2D to initialize from</param>
        public Point2D(Point2D that)
        {
            this.x = that.x;
            this.y = that.y;
        }

        /// <summary>
        /// Initializes a new instance of the Point2D struct.
        /// </summary>
        /// <param name="x">X coordinate.</param>
        /// <param name="y">Y coordinate.</param>
        public Point2D(double x, double y)
        {
            this.x = x;
            this.y = y;
        }

        /// <summary>
        /// Gets X.
        /// </summary>
        public double X
        {
            [MethodImpl(MethodImplOptions.AggressiveInlining)]
            get { return this.x; }
        }

        /// <summary>
        /// Gets Y.
        /// </summary>
        public double Y
        {
            [MethodImpl(MethodImplOptions.AggressiveInlining)]
            get { return this.y; }
        }

        /// <summary>
        /// Determine equality between a pair of Point2D instances.
        /// </summary>
        /// <param name="a">First Point2D instance.</param>
        /// <param name="b">Second Point2D instance.</param>
        /// <returns>Indication of equality.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static bool operator ==(Point2D a, Point2D b)
        {
            return (Math.Abs(a.x - b.x) < MathConstants.ErrorEpsilon &&
                    Math.Abs(a.y - b.y) < MathConstants.ErrorEpsilon);
        }

        /// <summary>
        ///  Subtract two points. 
        /// </summary>
        /// <param name="left">Left point.</param>
        /// <param name="right">Right point.</param>
        /// <returns>Vector resulting from left-right.</returns>
        public static Vector2 operator -(Point2D left, Point2D right)
        {
            return new Vector2(left.X - right.X, left.Y - right.Y);
        }

        /// <summary>
        /// Subtract a vector from a point.
        /// </summary>
        /// <param name="left">A point.</param>
        /// <param name="right">A vector.</param>
        /// <returns>Point that results from left-right.</returns>
        public static Point2D operator -(Point2D left, Vector2 right)
        {
            return new Point2D(left.X - right.X, left.Y - right.Y);
        }

        /// <summary>
        /// Add a vector to a point.
        /// </summary>
        /// <param name="left">A point.</param>
        /// <param name="right">A vector.</param>
        /// <returns>Point that results from left + right.</returns>
        public static Point2D operator +(Point2D left, Vector2 right)
        {
            return new Point2D(left.X + right.X, left.Y + right.Y);
        }

        /// <summary>
        /// Determine inequality between a pair of Point2D instances.
        /// </summary>
        /// <param name="a">First Point2D instance.</param>
        /// <param name="b">Second Point2D instance.</param>
        /// <returns>Indication of inequality.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static bool operator !=(Point2D a, Point2D b)
        {
            return (a.x != b.x || a.y != b.y);
        }

        /// <summary>
        /// Verifies whether the point is within the specified rectangle (top-left and bottom-right corners, inclusive)
        /// </summary>
        /// <param name="topLeft">The top-left corner of the bounding rectangle</param>
        /// <param name="bottomRight">The bottom-right corner of the bounding rectangle</param>
        /// <returns>True if the point falls within the specified rectangle, false otherwise</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public bool IsInBounds(Point2D topLeft, Point2D bottomRight)
        {
            return
                this.x < Math.Max(topLeft.x, bottomRight.x) &&
                this.x > Math.Min(topLeft.x, bottomRight.x) &&
                this.y < Math.Max(topLeft.y, bottomRight.y) &&
                this.y > Math.Min(topLeft.y, bottomRight.y);
        }

        /// <summary>
        /// Get hash code value.
        /// </summary>
        /// <returns>Hash code.</returns>
        public override int GetHashCode()
        {
            return this.x.GetHashCode() ^ this.y.GetHashCode();
        }

        /// <summary>
        /// Determine equality with another Point2D.
        /// </summary>
        /// <param name="that">Another Point2D to which to compare.</param>
        /// <returns>Indication of equality.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public bool Equals(Point2D that)
        {
            return (Math.Abs(this.x - that.x) < MathConstants.ErrorEpsilon &&
                    Math.Abs(this.y - that.y) < MathConstants.ErrorEpsilon);
        }

        /// <summary>
        /// Determine equality with another object.
        /// </summary>
        /// <param name="obj">Another Point2D object to which to compare.</param>
        /// <returns>Indication of equality.</returns>
        public override bool Equals(object obj)
        {
            Point2D other = (Point2D)obj;
            return this == other;
        }

        /// <summary>
        /// Generates a human-readable string representation
        /// </summary>
        /// <returns>A string representation</returns>
        public override string ToString()
        {
            return string.Format("<{0}, {1}>", this.x, this.y);
        }
    }
}
